#include "ObjParser.h"

#include "Model3D.h"

#include <fstream>
#include <string>
#include <sstream>
#include <vector>

#include <HSModel/TransformCoords.h>

namespace
  {
  /** 
  *  Splits i_s string into tokens by a delimiter. Tokens are saved in o_tokens vector
  *  if i_keep_empty is true, empty tokens are allowed
  */
  void split(std::vector<std::string> &o_tokens, const std::string &i_s,
    char i_delim, bool i_keep_empty = false)
    {
    std::string item;
    std::stringstream stream;
    stream << i_s;

    while (std::getline(stream, item, i_delim))
      {
      if(item != "" || i_keep_empty)
        o_tokens.push_back(item);
      }
    }

  /**
  * Gets point coordinates from i_tokens. i_count is a number of coordinates for point
  */
  std::vector<double> get_coordinates(std::vector<std::string> &i_tokens, int i_count)
    {
    std::vector<double> result;
    std::stringstream stream;
    double tmp;

    for(int i = 0; i < i_count; i++)
      {
      stream.clear();
      stream.str(i_tokens[i + 1]); // the first token is line type
      stream >> tmp;

      result.push_back(tmp);
      }


    return result;
    }

  /**
  * Fills o_array with vertice coordinates, texture etc indexes stored in i_tokens
  */
  void fill_vertice(int *o_array, std::vector<std::string> i_tokens)
    {
    std::stringstream stream;
    int i = 0; // coords first

    for(auto tok = i_tokens.begin(); tok != i_tokens.end(); tok++)
      {
      if(*tok != "")
        {
        stream.clear();
        stream << *tok;
        stream >> o_array[i];
        }
      i++;
      }
    }
  }

bool ImportObj(Model3D& o_model, const std::wstring& i_filename)
  {
  std::ifstream input(i_filename);
  if(!input)
    return false;

  std::vector<Point3DID> point_id_list;
  std::vector<TVector2> point_uv_list;
	std::vector<TVector3> point_normal_list;

  std::stringstream stream;

  std::stringstream buffer;
  buffer << input.rdbuf();
  std::string current_line;

  bool if_triangle_build_reached=false;
  while(std::getline(buffer, current_line))
    {

    std::vector<std::string> tokens;

    tokens.clear();
    split(tokens, current_line, ' ');
    if(tokens.empty())
      continue;

    if(tokens[0] == "v") // Vertice coordinates
      {
      auto coordinates = get_coordinates(tokens, 3);
      point_id_list.push_back(o_model.AddPoint(Coords::UIToWorld(TVector3(coordinates[0], coordinates[1], coordinates[2]))));
      }

    else if(tokens[0] == "vt") // Texture UV coords
      {
      auto coordinates = get_coordinates(tokens, 2);
      point_uv_list.push_back(TVector2(coordinates[0], 1 - coordinates[1]));
      }
		else if (tokens[0] == "vn") // normals
			{
			auto coordinates = get_coordinates(tokens, 3);
			point_normal_list.push_back(TVector3(coordinates[0], coordinates[1], coordinates[2]));
			}

    else if(tokens[0] == "f") // Face
      {
			std::vector<Point3DID> face_coords;
			std::vector<TVector2> face_textures;
			std::vector<TVector3> face_normals;

			std::vector<std::string> vertices;
      
			if_triangle_build_reached = true;

      vertices.clear();
      split(vertices, current_line, ' ');

      auto vert = ++vertices.begin(); // the first token is 'f'
      for(; vert != vertices.end(); vert++)
        {
        int v_data[3] = {0, 0, 0};
        tokens.clear();
        split(tokens, *vert, '/', true);

        fill_vertice(v_data, tokens);

        if(v_data[0] < 0 || v_data[1] < 0 || v_data[2] < 0) // Wrong obj file format: negative vertice indexes
          {
          return false;
          }

        face_coords.push_back(point_id_list[v_data[0] - 1]); // Count starts from 1 in obj file and from 0 in our vector

        if(v_data[1])
          face_textures.push_back(point_uv_list[v_data[1] - 1]); 
        else
          face_textures.push_back(TVector2()); // fallback
				if(v_data[2])
					face_normals.push_back(point_normal_list[v_data[2] - 1]);
				else 
					{
					const TVector3 DEFAULT_NORMAL(0, 0, 1);
					face_normals.push_back(DEFAULT_NORMAL);
					}
        }

      /* Generating triangles */

      for(size_t i = 2; i < face_coords.size() ; i++) // triangulation
        {
        auto id = o_model.AddTriangle(face_coords[0], face_coords[i-1], face_coords[i]);
        id.GetTriangle().SetPointsUV(face_textures[0], face_textures[i-1], face_textures[i]);
				id.GetTriangle().SetPointNormals(face_normals[0], face_normals[i-1], face_normals[i]);
        }

      face_coords.clear();
      face_textures.clear();
			face_normals.clear();
      }
    }

  if(point_id_list.size() == 0)
    return false;

  if(point_id_list.size() != 0 && !if_triangle_build_reached) 
    return false;

  return true;
  };